package org.bouncycastle.asn1.x500.style;

import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DERUniversalString;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.X500NameStyle;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class IETFUtils {
    public static RDN[] rDNsFromString(String name, X500NameStyle x500Style) {
        X500NameTokenizer nTok = new X500NameTokenizer(name);
        X500NameBuilder builder = new X500NameBuilder(x500Style);

        while (nTok.hasMoreTokens()) {
            String token = nTok.nextToken();
            int index = token.indexOf('=');

            if (index == -1) {
                throw new IllegalArgumentException(
                        "badly formated directory string");
            }

            String attr = token.substring(0, index);
            String value = token.substring(index + 1);
            ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr);

            if (value.indexOf('+') > 0) {
                X500NameTokenizer vTok = new X500NameTokenizer(value, '+');
                String v = vTok.nextToken();

                Vector oids = new Vector();
                Vector values = new Vector();

                oids.addElement(oid);
                values.addElement(v);

                while (vTok.hasMoreTokens()) {
                    String sv = vTok.nextToken();
                    int ndx = sv.indexOf('=');

                    String nm = sv.substring(0, ndx);
                    String vl = sv.substring(ndx + 1);

                    oids.addElement(x500Style.attrNameToOID(nm));
                    values.addElement(vl);
                }

                builder.addMultiValuedRDN(toOIDArray(oids),
                        toValueArray(values));
            } else {
                builder.addRDN(oid, value);
            }
        }

        return builder.build().getRDNs();
    }

    private static String[] toValueArray(Vector values) {
        String[] tmp = new String[values.size()];

        for (int i = 0; i != tmp.length; i++) {
            tmp[i] = (String) values.elementAt(i);
        }

        return tmp;
    }

    private static ASN1ObjectIdentifier[] toOIDArray(Vector oids) {
        ASN1ObjectIdentifier[] tmp = new ASN1ObjectIdentifier[oids.size()];

        for (int i = 0; i != tmp.length; i++) {
            tmp[i] = (ASN1ObjectIdentifier) oids.elementAt(i);
        }

        return tmp;
    }

    public static ASN1ObjectIdentifier decodeAttrName(String name,
            Hashtable lookUp) {
        if (Strings.toUpperCase(name).startsWith("OID.")) {
            return new ASN1ObjectIdentifier(name.substring(4));
        } else if (name.charAt(0) >= '0' && name.charAt(0) <= '9') {
            return new ASN1ObjectIdentifier(name);
        }

        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) lookUp.get(Strings
                .toLowerCase(name));
        if (oid == null) {
            throw new IllegalArgumentException("Unknown object id - " + name
                    + " - passed to distinguished name");
        }

        return oid;
    }

    public static ASN1Encodable valueFromHexString(String str, int off)
            throws IOException {
        str = Strings.toLowerCase(str);
        byte[] data = new byte[(str.length() - off) / 2];
        for (int index = 0; index != data.length; index++) {
            char left = str.charAt((index * 2) + off);
            char right = str.charAt((index * 2) + off + 1);

            if (left < 'a') {
                data[index] = (byte) ((left - '0') << 4);
            } else {
                data[index] = (byte) ((left - 'a' + 10) << 4);
            }
            if (right < 'a') {
                data[index] |= (byte) (right - '0');
            } else {
                data[index] |= (byte) (right - 'a' + 10);
            }
        }

        return ASN1Object.fromByteArray(data);
    }

    public static void appendTypeAndValue(StringBuffer buf,
            AttributeTypeAndValue typeAndValue, Hashtable oidSymbols) {
        String sym = (String) oidSymbols.get(typeAndValue.getType());

        if (sym != null) {
            buf.append(sym);
        } else {
            buf.append(typeAndValue.getType().getId());
        }

        buf.append('=');

        buf.append(valueToString(typeAndValue.getValue()));
    }

    public static String valueToString(ASN1Encodable value) {
        StringBuffer vBuf = new StringBuffer();

        if (value instanceof ASN1String
                && !(value instanceof DERUniversalString)) {
            String v = ((ASN1String) value).getString();
            if (v.length() > 0 && v.charAt(0) == '#') {
                vBuf.append("\\" + v);
            } else {
                vBuf.append(v);
            }
        } else {
            vBuf.append("#"
                    + bytesToString(Hex.encode(value.getDERObject()
                            .getDEREncoded())));
        }

        int end = vBuf.length();
        int index = 0;

        if (vBuf.length() >= 2 && vBuf.charAt(0) == '\\'
                && vBuf.charAt(1) == '#') {
            index += 2;
        }

        while (index != end) {
            if ((vBuf.charAt(index) == ',') || (vBuf.charAt(index) == '"')
                    || (vBuf.charAt(index) == '\\')
                    || (vBuf.charAt(index) == '+')
                    || (vBuf.charAt(index) == '=')
                    || (vBuf.charAt(index) == '<')
                    || (vBuf.charAt(index) == '>')
                    || (vBuf.charAt(index) == ';')) {
                vBuf.insert(index, "\\");
                index++;
                end++;
            }

            index++;
        }

        return vBuf.toString();
    }

    private static String bytesToString(byte[] data) {
        char[] cs = new char[data.length];

        for (int i = 0; i != cs.length; i++) {
            cs[i] = (char) (data[i] & 0xff);
        }

        return new String(cs);
    }

    public static String canonicalize(String s) {
        String value = Strings.toLowerCase(s.trim());

        if (value.length() > 0 && value.charAt(0) == '#') {
            DERObject obj = decodeObject(value);

            if (obj instanceof ASN1String) {
                value = Strings.toLowerCase(((ASN1String) obj).getString()
                        .trim());
            }
        }

        value = stripInternalSpaces(value);

        return value;
    }

    private static ASN1Object decodeObject(String oValue) {
        try {
            return ASN1Object.fromByteArray(Hex.decode(oValue.substring(1)));
        } catch (IOException e) {
            throw new IllegalStateException("unknown encoding in name: " + e);
        }
    }

    public static String stripInternalSpaces(String str) {
        StringBuffer res = new StringBuffer();

        if (str.length() != 0) {
            char c1 = str.charAt(0);

            res.append(c1);

            for (int k = 1; k < str.length(); k++) {
                char c2 = str.charAt(k);
                if (!(c1 == ' ' && c2 == ' ')) {
                    res.append(c2);
                }
                c1 = c2;
            }
        }

        return res.toString();
    }
}
